/* Written by Zachary Salzbank, Erican Sponsler and Nate Weiss */

%{ open Ast %}

%token INT CHAR BOOLEAN VOID
%token NODE
%token SEMI LPAREN RPAREN LBRACE RBRACE COMMA LBRACKET RBRACKET
%token PLUS MINUS TIMES DIVIDE ASSIGN
%token EQ NEQ LT LEQ GT GEQ
%token BOOLAND BOOLOR BANG
%token VALUEOF
%token RETURN IF ELSE WHILE BREAK
%token NULL_LITERAL
%token <bool> BOOLEAN_LITERAL
%token <char> CHAR_LITERAL
%token <int> INTEGER_LITERAL
%token <string> ID
%token EOF

%nonassoc NOELSE
%nonassoc ELSE
%nonassoc BANG
%right ASSIGN
%left EQ NEQ
%left BOOLAND BOOLOR
%left LT GT LEQ GEQ
%left PLUS MINUS
%left TIMES DIVIDE
%left VALUEOF

%start program
%type <Ast.program> program

%%

program:
   /* nothing */ { [], [] }
 | program vdecl { ($2 :: fst $1), snd $1 }
 | program fdecl { fst $1, ($2 :: snd $1) }

fdecl:
   func_decl LPAREN formals_opt RPAREN LBRACE vdecl_list stmt_list RBRACE
   { { ftype = snd $1;
         fname = fst $1; 
	 formals = $3;
	 locals = List.rev $6;
	 body = List.rev $7 } }

obj_type:
    INT                       { IntType }
  | CHAR                      { CharType }
  | BOOLEAN                   { BooleanType }
  | NODE LT obj_type GT       { NodeType($3) }

obj_decl:
    obj_type ID      { ($2, $1) }

func_decl:
    VOID ID   { ($2, VoidType) } /* Need this because can't declare anything but function as void */
  | obj_decl  { $1 }

formals_opt:
    /* nothing */ { [] }
  | formal_list   { List.rev $1 }

formal_list:
    obj_decl                   { [{ vname = fst $1; vtype = snd $1; vdefault = None; }] }
  | formal_list COMMA obj_decl { ({ vname = fst $3; vtype = snd $3; vdefault = None; }) :: $1 }

vdecl_list:
    /* nothing */    { [] }
  | vdecl_list vdecl { $2 :: $1 }

vdecl:
    obj_decl SEMI { { vname = fst $1; vtype = snd $1; vdefault = None; } }

stmt_list:
    /* nothing */  { [] }
  | stmt_list stmt { $2 :: $1 }

stmt:
    expr SEMI { Expr($1) }
  | RETURN expr SEMI { Return($2) }
  | RETURN SEMI { Return(Noexpr) }
  | LBRACE vdecl_list stmt_list RBRACE { Block(List.rev $2, List.rev $3) }
  | IF LPAREN expr RPAREN stmt %prec NOELSE { If($3, $5, Block([], [])) }
  | IF LPAREN expr RPAREN stmt ELSE stmt    { If($3, $5, $7) }
  | WHILE LPAREN expr RPAREN stmt { While($3, $5) }

literals:
    INTEGER_LITERAL                           { Integer($1) }
  | CHAR_LITERAL                              { Character($1) }  
  | BOOLEAN_LITERAL                           { Boolean($1) }
  | NULL_LITERAL                              { Null }  

l_value:
    ID                                        { Id($1) }
  | l_value VALUEOF                           { Unop($1, ValueOf) }
  | l_value LBRACKET expr RBRACKET            { Unop($1, Child($3)) }

expr:
    literals                                  { Literal($1) }
  | LT expr GT                                { Node($2) }
  | l_value                                   { LValue($1) }
  | MINUS expr                                { Neg($2) }
  | BANG expr                                 { Bang($2) }
  | expr BOOLAND expr                         { Binop($1, BoolAnd, $3) }
  | expr BOOLOR expr                          { Binop($1, BoolOr, $3) }
  | expr PLUS   expr                          { Binop($1, Add,   $3) }
  | expr MINUS  expr                          { Binop($1, Sub,   $3) }
  | expr TIMES  expr                          { Binop($1, Mult,  $3) }
  | expr DIVIDE expr                          { Binop($1, Div,   $3) }
  | expr EQ     expr                          { Binop($1, Equal, $3) }
  | expr NEQ    expr                          { Binop($1, Neq,   $3) }
  | expr LT     expr                          { Binop($1, Less,  $3) }
  | expr LEQ    expr                          { Binop($1, Leq,   $3) }
  | expr GT     expr                          { Binop($1, Greater,  $3) }
  | expr GEQ    expr                          { Binop($1, Geq,   $3) }
  | l_value ASSIGN expr                       { Assign($1, $3) }
  | ID LPAREN actuals_opt RPAREN              { Call($1, $3) }
  | LPAREN expr RPAREN                        { $2 }

actuals_opt:
    /* nothing */ { [] }
  | actuals_list  { List.rev $1 }

actuals_list:
    expr                    { [$1] }
  | actuals_list COMMA expr { $3 :: $1 }
